### 1. Объявите в программе класс с именем Rect (прямоугольник), объекты которого создаются командой:

```python
rect = Rect(x, y, width, height)
```

где `x, y` - координата верхнего левого угла (числа: целые или вещественные);

`width, height` - ширина и высота прямоугольника (числа: целые или вещественные).

В этом классе определите магический метод, чтобы хэши объектов класса Rect с равными width, height были равны.

Например:

```python
r1 = Rect(10, 5, 100, 50)
r2 = Rect(-10, 4, 100, 50)

h1, h2 = hash(r1), hash(r2)   # h1 == h2
```

<details>
<summary>Решение </summary>

```python
class Rect:
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height

    def __hash__(self):
        return hash((self.width, self.height))
 

r1 = Rect(10, 5, 100, 50)
r2 = Rect(-10, 4, 100, 50)


h1, h2 = hash(r1), hash(r2)
print(h1==h2)

```
</details>

### 2. Объявите класс с именем ShopItem (товар), объекты которого создаются командой:

> item = ShopItem(name, weight, price)

где
`name` - название товара (строка); 
`weight` - вес товара (число: целое или вещественное);
`price` - цена товара (число: целое или вещественное).

### Определите в этом классе магические методы:

`__hash__()` - чтобы товары с одинаковым названием (без учета регистра), весом и ценой имели бы равные хэши;
`__eq__()` - чтобы объекты с одинаковыми хэшами были равны.

Затем, из входного потока прочитайте строки командой:

> lst_in = list(map(str.strip, sys.stdin.readlines()))

Строки имеют следующий формат:
```
название товара 1: вес_1 цена_1
...
название товара N: вес_N цена_N
```

Например:

```
Системный блок: 1500 75890.56
Монитор Samsung: 2000 34000
Клавиатура: 200.44 545
Монитор Samsung: 2000 34000
```
Как видите, товары в этом списке могут совпадать.

Необходимо для всех этих строчек сформировать соответствующие объекты класса ShopItem и добавить в словарь с именем shop_items. 

Ключами словаря должны выступать сами объекты, а значениями - список в формате:

> [item, total]

где

`item` - объект класса ShopItem; 
`total` - общее количество одинаковых объектов (с одинаковыми хэшами). 

Подумайте, как эффективно программно наполнять такой словарь, проходя по списку `lst_in` один раз.


`Sample Input:`
```
Системный блок: 1500 75890.56
Монитор Samsung: 2000 34000
Клавиатура: 200.44 545
Монитор Samsung: 2000 34000
```

<details>
<summary>Решение </summary>

```python
class ShopItem:

    def __init__(self, name, weight, price):
        self.name = name
        self.weight = weight
        self.price = price


    def __hash__(self):
        return hash((self.name.lower(), self.weight, self.price))

    def __eq__(self, other):
        return hash(self) == hash(other)


lst_in = ['Системный блок: 1500 75890.56',
          'Монитор Samsung: 2000 34000',
          'Клавиатура: 200.44 545',
          'Монитор Samsung: 2000 34000']

shop_items = {}


for product in lst_in:
    name, weight, price = product.rsplit(maxsplit=2)
    prod = ShopItem(name[:-1], weight, price)
    shop_items.setdefault(prod, [prod, 0])[1] += 1
    # total = 1
    # if prod in shop_items:
    #     total += 1
    #     shop_items[prod] = [prod, total]
    # else:
    #     shop_items[prod] = [prod, total]
    #     total = shop_items

it1 = ShopItem('name', 10, 11)
it2 = ShopItem('name', 10, 11)
assert hash(it1) == hash(it2), "разные хеши у равных объектов"
it2 = ShopItem('name', 10, 12)

assert hash(it1) != hash(it2), "равные хеши у разных объектов"

it2 = ShopItem('name', 11, 11)
assert hash(it1) != hash(it2), "равные хеши у разных объектов"

it2 = ShopItem('NAME', 10, 11)
assert hash(it1) == hash(it2), "разные хеши у равных объектов"

name = lst_in[0].split(':')
for sp in shop_items.values():
    assert isinstance(sp[0], ShopItem) and type(sp[1]) == int, "в значениях словаря shop_items первый элемент должен быть объектом класса ShopItem, а второй - целым числом"

v = list(shop_items.values())
if v[0][0].name.strip() == "Системный блок":
    assert v[0][1] == 1 and v[1][1] == 2 and v[2][1] == 1 and len(v) == 3, "неверные значения в словаре shop_items"

if v[0][0].name.strip() == "X-box":
    assert v[0][1] == 2 and v[1][1] == 1 and v[2][1] == 2 and len(v) == 3, "неверные значения в словаре shop_items"
```
</details>


### 2. Создается проект, в котором предполагается использовать списки из целых чисел. 

Для этого вам ставится задача создать класс с именем `ListInteger` с базовым классом `list` и переопределить три метода:

```python
__init__()
__setitem__()
append()
```

так, чтобы список `ListInteger` содержал только целые числа. 

При попытке присвоить любой другой тип данных, генерировать исключение командой:

```python
raise TypeError('можно передавать только целочисленные значения')
```

Пример использования класса ListInteger 


```python
s = ListInteger((1, 2, 3))
s[1] = 10
s.append(11)
print(s)
s[0] = 10.5 # TypeError
```


<details>
<summary>Решение </summary>

```python
class ListInteger(list):

    def __init__(self, obj):
        super().__init__(obj)

    def append(self, value):
        self.__check(value)
        super().append(value)

    def __setitem__(self, key, value):
        self.__check(value)
        super().__setitem__(key, value)

    @staticmethod
    def __check(*args):
        for arg in args:
            if not isinstance(arg, int):
                raise TypeError('можно передавать только целочисленные значения')
```
</details>


### Разрабатывается интернет-магазин. 

Каждый товар предполагается представлять классом Thing, объекты которого создаются командой:

```python
thing = Thing(name, price, weight)
```

где `name` - наименование товара (строка); 
`price` - цена (вещественное число); 
`weight` - вес товара (вещественное число). 

В каждом объекте этого класса создаются аналогичные атрибуты: `name, price, weight`.

Класс `Thing` необходимо определить так, чтобы его объекты можно было использовать в качестве ключей словаря, например:

```python
d = {}
d[thing] = thing
```

И для каждого уникального набора данных `name, price, weight` должны формироваться свои уникальные ключи.

Затем, вам необходимо объявить класс словаря `DictShop`, унаследованный от базового класса `dict`.

В этом новом словаре ключами могут выступать только объекты класса `Thing`. При попытке указать любой другой тип, генерировать исключение командой:

```python
raise TypeError('ключами могут быть только объекты класса Thing')
```
Объекты класса DictShop должны создаваться командами:
```python
dict_things = DictShop() # пустой словарь
dict_things = DictShop(things) # словарь с набором словаря things
```
где `things` - некоторый словарь.

В инициализаторе следует проверять, чтобы аргумент thing был словарем, если не так, то выбрасывать исключение:
```python
raise TypeError('аргумент должен быть словарем')
```
И проверять, чтобы все ключи являлись объектами класса `Thing`. 

Если это не так, то генерировать исключение:
```python
raise TypeError('ключами могут быть только объекты класса Thing')
```
Дополнительно в классе DictShop переопределить метод:

`__setitem__()`

с проверкой, что создаваемый ключ является объектом класса Thing. 

Иначе, генерировать исключение:
```python
raise TypeError('ключами могут быть только объекты класса Thing')
```
Пример использования классов:
```python
th_1 = Thing('Лыжи', 11000, 1978.55)
th_2 = Thing('Книга', 1500, 256)
dict_things = DictShop()
dict_things[th_1] = th_1
dict_things[th_2] = th_2

for x in dict_things:
    print(x.name)

dict_things[1] = th_1 # исключение TypeError

```


<details>
<summary>Решение </summary>

```python


class Thing:
    def __init__(self, name, price, weight):
        self.name = name
        self.price = price
        self.weight = weight
    def __hash__(self) -> int:
        return hash((self.name, self.price, self.weight))
    

class DictShop(dict):
    def __init__(self, things=None):
        self.things = things if things else {}
        if not isinstance(self.things, dict):
            raise TypeError('аргумент должен быть словарем')
        if self.things and not all(isinstance(key, Thing) for key in self.things):
            raise TypeError('ключами могут быть только объекты класса Thing')
        super().__init__(self.things)
    
    def __setitem__(self, __key, __value):
        if not isinstance(__key, Thing):
            raise TypeError('ключами могут быть только объекты класса Thing')
        super().__setitem__(__key, __value)
      
        
        
th_1 = Thing('Лыжи', 11000, 1978.55)
th_2 = Thing('Книга', 1500, 256)


dict_things = DictShop()
dict_things[th_1] = th_1
dict_things[th_2] = th_2

for x in dict_things:
    print(x.name)
# dict_things = DictShop() # пустой словарь
# dict_things = DictShop(things) # словарь с набором словаря things
```
</details>